﻿//-----------------------------------------------------------------------------------------------------------
// WBFSSync Project by Omega Frost 
// http://wbfssync.codeplex.com/
//
// WBFSSync is Licensed under the terms of the 
// Microsoft Reciprocal License (Ms-RL)
//-----------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace WBFSe3.Streams
{
    //-------------------------------------------------------------------------------------------------------
    //
    //-------------------------------------------------------------------------------------------------------
    public class WbfsSectorStream : WbfsStream
    {
        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        WbfsStream stream;
        ushort[] sectors;
        int sectorIdx;
        int offset;
        long position;
        long length;


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public override string Name
        {
            get { return this.stream.Name; }
        }

        public override bool CanRead
        {
            get { return this.stream.CanRead; }
        }

        public override bool CanSeek
        {
            get { return this.stream.CanSeek; }
        }

        public override bool CanWrite
        {
            get { return this.stream.CanWrite; }
        }

        public override long Length
        {
            get { return this.length; }
        }

        public override long Position
        {
            get { return this.position; }
            set { Seek(value, SeekOrigin.Begin); }
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public WbfsSectorStream(WbfsStream stream, int sectorSize, ushort[] sectors)
            : base(sectorSize)
        {
            this.stream = stream;
            this.sectorSize = sectorSize;
            this.sectors = new ushort[sectors.Length];
            this.length = (long)sectorSize * sectors.Length;
            this.position = -1;

            sectors.CopyTo(this.sectors, 0);

            Seek(0, SeekOrigin.Begin);
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public override void Flush()
        {
            this.stream.Flush();
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public override void Close()
        {
            this.stream.Close();
            base.Close();
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public override void SetLength(long value)
        {
            /* Nops... */
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public override long Seek(long offset, SeekOrigin origin)
        {
            switch (origin)
            {
                case SeekOrigin.Current: offset += this.Position; break;
                case SeekOrigin.End: offset = this.Length - offset; break;
            }

            if (offset != this.position)
            {
                this.sectorIdx = (int)(offset / this.sectorSize);
                long sector = this.sectors[this.sectorIdx];
                this.offset = (int)(offset % this.sectorSize);
                this.position = offset;

                this.stream.Seek(sector * this.sectorSize + this.offset, SeekOrigin.Begin);
            }

            return position;
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public override int Read(byte[] buffer, int offset, int count)
        {
            if (this.position + count >= this.length)
                throw new EndOfStreamException();

            int read = 0;
            while(read != count)
            {
                int max = this.sectorSize - this.offset;
                int toread = count < max ? count : max;

                if (this.sectors[this.sectorIdx] == 0) Array.Clear(buffer, read, toread);
                else this.stream.Read(buffer, offset + read, toread);

                read += toread;
                this.offset += toread;

                if (toread == max)
                {
                    this.offset = 0;

                    if ((++this.sectorIdx < this.sectors.Length) && (this.sectors[this.sectorIdx] != 0))
                        this.stream.Seek((long)this.sectors[this.sectorIdx] * this.sectorSize, SeekOrigin.Begin);
                }
            }

            this.position += read;
            return count;
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public override void Write(byte[] buffer, int offset, int count)
        {
            if (this.position + count >= this.length)
                throw new EndOfStreamException();

            int written = 0;
            while (written != count)
            {
                int max = this.sectorSize - this.offset;
                int towrite = count < max ? count : max;
                
                if (this.sectors[this.sectorIdx] == 0) throw new ArgumentOutOfRangeException();
                else this.stream.Write(buffer, offset + written, towrite);

                written += towrite;
                this.offset += towrite;

                if (towrite == max)
                {
                    this.offset = 0;

                    if ((++this.sectorIdx < this.sectors.Length) && (this.sectors[this.sectorIdx] != 0))
                        this.stream.Seek((long)this.sectors[this.sectorIdx] * this.sectorSize, SeekOrigin.Begin);
                }
            }

            this.position += written;
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public override int ReadSector(int sector, IntPtr array, int offset)
        {
            this.sectorIdx = sector + 1;
            this.position += this.sectorSize;
            return this.stream.ReadSector(this.sectors[sector], array, offset);
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public override int WriteSector(int sector, IntPtr array, int offset)
        {
            this.sectorIdx = sector + 1;
            this.position += this.sectorSize;
            return this.stream.WriteSector(this.sectors[sector], array, offset);
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public override void WriteEmptyBlock(int count)
        {
            if ((this.position % this.sectorSize) != 0)
                throw new ArgumentOutOfRangeException();

            if (this.sectors[this.sectorIdx] != 0)
            {
                Byte[] zero = new Byte[this.sectorSize];
                this.stream.Write(zero, 0, this.sectorSize);
            }
            else
            {
                this.offset = 0;
                this.position += this.sectorSize;
                this.sectorIdx += count / this.sectorSize;
            }
        }
    }
}
